<?php

// +----------------------------------------------------------------------
// | 订单相关的业务
// +----------------------------------------------------------------------
// | @author fancsen
// +----------------------------------------------------------------------

namespace app\api\service;

use app\common\lib\exception\ApiException;
use app\api\model\Product as ProductModel;
use app\api\model\UserAddress as AddressModel;
use app\api\model\Order as OrderModel;
use app\api\model\OrderProduct as OrderProductModel;
use app\common\lib\enum\OrderStatusEnum;
use app\api\service\DeliveryMessage;
use think\Db;


class Order
{
    // 下订单支付业务流程
    // +----------------------------------------------------------------------
    // | 用户选择商品后，api向服务器提交商品信息
    // +----------------------------------------------------------------------
    // | api接收商品信息后，检查商品库存
    // +----------------------------------------------------------------------
    // | 库存通过，将订单信息加入数据库 == 下订单，下单成功，api向客户端返回信息，可以支付了
    // +----------------------------------------------------------------------
    // | 调用支付接口进行支付
    // +----------------------------------------------------------------------
    // | 支付过程中库存可能减少，再次检查库存
    // +----------------------------------------------------------------------
    // | 库存通过，服务器调用支付接口进行支付
    // +----------------------------------------------------------------------
    // | 微信返回支付结果（异步）
    // +----------------------------------------------------------------------
    // | 成功：最后一次检查库存量
    // +----------------------------------------------------------------------
    // | 成功：进行库存量扣除
    // +----------------------------------------------------------------------

    // 创建订单业务流程
    // +----------------------------------------------------------------------
    // | 订单创建时刻立即生成订单快照，保存下单时刻的商品信息
    // +----------------------------------------------------------------------  
    // | 创建订单
    // +----------------------------------------------------------------------  

    //用户通过api传递过来的商品信息
    protected $oProducts;
    //从数据库中得到的商品信息
    protected $dbProducts;
    //用户id
    protected $uid;

    /**
     * 下单
     * @param int $uid 用户id
     * @param array $oProducts=>[
     *      ['product_id'=>1,count=>1],
     *      ['product_id'=>2,count=>3],
     *      ['product_id'=>3,count=>2],
     * ]
     * @return 
     */
    public function place($uid,$oProducts)
    {
        $this->oProducts = $oProducts;
        $this->uid = $uid;
        $this->dbProducts =$this->getProductsByOrder($oProducts);
        //获取订单信息
        $status = $this->getOrderStatus();
        //如果订单状态不可用
        if (!$status['pass']){
            $status['order_id'] = -1;
            return $status;
        }
        //检测通过，创建订单
        $snap = $this->snapOrder($status);//生成订单快照
        $order = $this->createOrder($snap);//创建订单
        $order['pass']= true;
        return $order;
    }

    /**
     * 创建订单
     * @param array $snap 订单快照数据
     * @return array 
     */
    private function createOrder($snap)
    {
        // 启动事务
        Db::startTrans();
        try{
            $orderNo = $this->makeOrderNo();
            //订单入库操作
            $order = new OrderModel();
            $order->order_no = $orderNo;
            $order->user_id = $this->uid;
            $order->total_price = $snap['orderPrice'];
            $order->total_count = $snap['totalCount'];
            $order->snap_img = $snap['snapImg'];
            $order->snap_name = $snap['snapName'];
            $order->snap_address = $snap['snapAddress'];
            $order->snap_items = json_encode($snap['pStatus']);
            $order->save();
            //订单和商品多对多关系表插入数据
            foreach ($this->oProducts as &$value) {
                $value['order_id'] = $order->id;
            }
            $orderProduct = new OrderProductModel();
            $orderProduct->saveAll($this->oProducts);
            // 提交事务
            Db::commit(); 
            return [
                'order_no' => $orderNo,
                'order_id' => $order->id,
                'create_time' => $order->create_time
            ];
        }catch(\Exception $e){
            // 回滚事务
            Db::rollback();
            throw $e;
        }
    }

    /**
     * 生产唯一订单号
     */
    private function makeOrderNo()
    {
        $yCode = array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J');
        $orderSn =
            $yCode[intval(date('Y')) - 2018] .
            strtoupper(dechex(date('m'))) .
            date('d') .
            substr(time(), -5) .
            substr(microtime(), 2, 5) .
            sprintf('%02d', rand(0, 99));
        return $orderSn;
    }

    /**
     * 生成订单快照
     * @param array $status 
     * @return array 快照信息数组
     */
    private function snapOrder($status)
    {
        //定义一个默认的数组用于接收订单快照
        $snap = [
            'orderPrice' => 0,
            'totalCount' => 0,
            'pStatus'    => [],
            'snapAddress' => '',
            'snapName' => '',
            'snapImg' => ''
        ];

        //给订单快照赋值
        $snap['orderPrice'] = $status['orderPrice'];
        $snap['totalCount'] = $status['totalCount'];
        $snap['pStatus'] = $status['pStatusArray'];
        //如果需要做订单检索，最好使用mongo等数据库存储为对象
        $snap['snapAddress'] = json_encode($this->getUserAddress());
        $snap['snapName'] = $this->dbProducts[0]['name'];
        $snap['snapImg'] = $this->dbProducts[0]['main_img_url'];
        //如果商品有多种，则在商品名后加个‘等’
        if(count($this->dbProducts) > 1){
            $snap['snapName'] .= '等';
        }

        return $snap;
    }

    /**
     * 获取用户收货地址
     */
    private function getUserAddress()
    {
        $address = AddressModel::where('user_id','=',$this->uid)->find();

        if(!$address){
            throw new ApiException("用户收货地址不存在，创建订单失败", 404,80001);
            
        }
        return $address;
    }

    /**
     * 获取订单信息and检查商品库存
     */
    private function getOrderStatus()
    {
        //定义一个默认的数组用于接收订单状态以及订单信息
        $status = [
            'pass'=>true,   //是否通过检测
            'orderPrice'=>0,//订单总价
            'totalCount'=>0,//订单商品总数
            'pStatusArray'=>[]//订单商品数组
        ];

        //循环检测订单商品信息和真实商品对比
        foreach ($this->oProducts as $oProduct) {
            //获取商品信息状态和信息
            $product = $this->getProduct(
                $oProduct['product_id'],$oProduct['count'],$this->dbProducts
            );
            //如果库存不足，则检测不通过
            if(!$product['haveStock']){
                $status['pass'] = false;
            }
            //订单状态数组赋值
            $status['orderPrice'] +=  $product['totalPrice'];
            $status['totalCount'] +=  $product['count'];
            array_push($status['pStatusArray'],$product);
        }
        return $status;
    }

    /**
     * 检测商品信息-库存
     * @param int 订单商品id
     * @param int 订单数量
     * @param array 真实商品信息
     * @return array 商品信息
     */
    private function getProduct($oPid,$oCount,$products)
    {
        //定义一个默认的数组用于接收商品状态和信息
        $pStatus = [
            'id'=>null,
            'count'=>0,
            'price'=>0,
            'name'=>'',
            'main_img_url'=>'',
            'totalPrice'=>0,
            'haveStock'=>false,//是否有足够库存
        ];

        //用户传入进来的商品和真实商品作比对，
        $pIndex = -1;
        for ($i=0; $i < count($products); $i++) { 
            if($oPid == $products[$i]['id']){
                $pIndex = $i;
            }
        }
        if($pIndex == -1){
            throw new ApiException('id为'.$oPid.'商品不存在，创建订单失败', 403,80000);
        }else {
            //完成商品信息赋值
            $product = $products[$pIndex];
            $pStatus['id'] = $oPid;
            $pStatus['count'] = $oCount;
            $pStatus['price'] = $product['price'];
            $pStatus['name'] = $product['name'];
            $pStatus['main_img_url'] = $product['main_img_url'];
            $pStatus['totalPrice'] = $product['price'] * $oCount;
            //是否有库存
            if($product['stock'] >= $oCount){
                $pStatus['haveStock'] = true;
            }
        }
        return $pStatus;

    }


    /**
     * 检查商品库存
     * @param int $orderID订单id号
     * @return array 商品状态信息
     */
    public function checkOrderStock($orderID)
    {
        $op = OrderProductModel::where(['order_id'=>$orderID])->select();

        $this->oProducts = $op;

        $this->dbProducts = $this->getProductsByOrder($this->oProducts);

        $status = $this->getOrderStatus();

        return $status;
    }

    /**
     * 根据订单信息查询数据库中商品信息
     */
    private function getProductsByOrder($oProducts)
    {
        $oPids = [];
        foreach ($oProducts as $key => $value) {
            array_push($oPids,$value['product_id']);
        }

        $products = ProductModel::all($oPids)
            ->visible(['id', 'price', 'stock', 'name', 'main_img_url'])
            ->toArray();
        return $products;
    }

    /**
     * 订单发货
     * @param int 订单id
     * @return string bool
     */
    public function delivery($orderId,$jumpPage='')
    {
        $order = OrderModel::get($orderId);
        if(!$order){
            throw new ApiException("订单不存在，请查id", 403,80003); 
        }
        //检查订单状态
        if($order->status != orderStatusEnum::PAID){
            throw new ApiException("订单尚未支付", 403,80006);
            
        }
        //修改订单为发货状态
        $order->status = orderStatusEnum::DELIVERED;
        $order->save();
        //发送模版消息
        $message =  new DeliveryMessage();
        $message->sendDeliveryMessage($order,$jumpPage);
    }
}
